import requests
import pandas as pd
import random
import json
import os
import logging
from tqdm import tqdm
import concurrent.futures
import time
import sys

# 配置日志，确保中文正常显示
logging.basicConfig(
    filename='vivoApkDownload20250917.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    encoding='utf-8'  # 指定编码为UTF-8
)
# 同时输出到控制台
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)

def download_file(url, save_path, headers=None):
    """
    从URL下载文件并保存到指定路径，显示下载进度条
    :param url: 文件的URL
    :param save_path: 保存路径
    :param headers: 请求头
    :return: 是否下载成功
    """
    # 检查文件是否已存在
    if os.path.exists(save_path):
        file_size = os.path.getsize(save_path)
        if file_size > 10240:  # 如果文件大于10KB，认为是有效文件
            logging.info(f"文件已存在，跳过下载: {save_path}，大小: {file_size/1024/1024:.2f} MB")
            return True
        else:
            logging.warning(f"文件已存在但大小异常 ({file_size/1024:.2f} KB)，将重新下载")
            # 删除小文件，重新下载
            try:
                os.remove(save_path)
            except Exception as e:
                logging.error(f"删除文件失败: {e}")
    
    if not url:
        logging.error(f"下载URL为空，无法下载")
        return False
    
    # 确保保存文件的目录存在
    os.makedirs(os.path.dirname(os.path.abspath(save_path)), exist_ok=True)
    
    # 设置下载专用请求头
    download_headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Referer': 'https://h5.appstore.vivo.com.cn/'
    }
    
    try:
        # 首先发送HEAD请求检查URL是否有效
        logging.debug(f"检查下载URL: {url}")
        head_response = requests.head(url, headers=download_headers, timeout=30, allow_redirects=True)
        logging.debug(f"HEAD请求状态码: {head_response.status_code}")
        
        if head_response.status_code != 200:
            logging.warning(f"HEAD请求失败，状态码：{head_response.status_code}")
            logging.info(f"尝试直接发送GET请求...")
        
        # 发送GET请求，启用流模式
        response = requests.get(url, headers=download_headers, stream=True, timeout=60, verify=True, allow_redirects=True)
        
        # 打印响应信息用于调试
        logging.debug(f"Response status code: {response.status_code}")
        logging.debug(f"Content-Type: {response.headers.get('Content-Type')}")
        logging.debug(f"Content-Length: {response.headers.get('Content-Length')}")
        
        # 检查请求是否成功
        if response.status_code != 200:
            logging.error(f"请求失败，状态码：{response.status_code}")
            return False
            
        # 检查内容类型，确保是APK文件
        content_type = response.headers.get('Content-Type')
        if content_type and 'application/vnd.android.package-archive' not in content_type:
            logging.warning(f"警告: 响应内容类型不是APK文件: {content_type}")
            # 尝试读取一小部分响应内容来判断是否是错误消息
            content_preview = response.raw.read(100)
            logging.debug(f"响应内容预览: {content_preview}")
            response.raw.seek(0)  # 重置读取位置
        
        # 获取总文件大小（如果存在）
        total_size = int(response.headers.get('content-length', 0))
        
        # 如果文件大小过小，可能不是有效的APK文件
        if total_size < 10240:  # 小于10KB可能不是有效APK
            logging.warning(f"警告: 文件大小仅为 {total_size/1024:.2f} KB，可能不是完整的APK文件")
            # 如果文件太小，尝试读取全部内容看看是什么
            if total_size < 1024:
                content = response.content
                logging.debug(f"响应内容: {content}")
                # 可能是JSON错误消息，尝试解析
                try:
                    error_json = json.loads(content)
                    logging.debug(f"解析的JSON响应: {error_json}")
                except:
                    pass
            return False
        
        # 使用 tqdm 显示进度条
        with open(save_path, 'wb') as file, tqdm(
                desc=os.path.basename(save_path),
                total=total_size,
                unit='iB',
                unit_scale=True,
                unit_divisor=1024,
        ) as bar:
            for data in response.iter_content(chunk_size=8192):  # 增加块大小提高效率
                if data:
                    size = file.write(data)
                    bar.update(size)
        
        # 验证下载的文件大小
        downloaded_size = os.path.getsize(save_path)
        if downloaded_size < 10240:  # 小于10KB可能不是完整的APK
            logging.error(f"下载的文件大小仅为 {downloaded_size/1024:.2f} KB，可能下载不完整")
            # 读取文件内容看看是什么
            with open(save_path, 'rb') as f:
                content = f.read()
            logging.debug(f"文件内容: {content}")
            return False
            
        logging.info(f"文件已成功下载并保存到 {save_path}，大小: {downloaded_size/1024/1024:.2f} MB")
        return True
    except Exception as e:
        logging.error(f"下载过程中发生错误：{e}")
        return False

def process_app(app_name, base_url, headers):
    """
    处理单个应用的搜索和下载
    :param app_name: 应用名称
    :param base_url: 基础URL
    :param headers: 请求头
    """
    logging.info(f"处理应用: {app_name}")
    try:
        # 发送GET请求获取应用信息
        search_url = base_url + app_name
        logging.debug(f"搜索URL: {search_url}")
        response = requests.get(search_url, headers=headers, timeout=30)
        
        # 检查请求是否成功
        if response.status_code != 200:
            logging.error(f"搜索应用 '{app_name}' 失败: {response.status_code}")
            return False
            
        # 将响应内容转换成JSON对象
        response_data = response.json()
        logging.debug(f"搜索响应: {json.dumps(response_data, ensure_ascii=False)[:200]}...")
        
        # 提取所需数据
        if 'data' in response_data:
            data = (
                response_data
                .get('data', {})
                .get('appSearchSugResponse', {})
                .get('apps', [])
            )

            if not isinstance(data, list) or len(data) == 0:
                logging.warning(f"未找到应用 '{app_name}' 的下载链接")
                return False

            # 获取第一个应用的信息
            app_info = data[0]
            download_url = app_info.get('download_url')
            
            if not download_url:
                logging.warning(f"未找到应用 '{app_name}' 的下载链接")
                return False
                
            logging.info(f"获取到下载链接: {download_url}")
                
            # 获取应用名称、版本和包名
            app_name_real = app_info.get('name', app_name)
            app_version = app_info.get('version_name', 'unknown')
            package_name = app_info.get('package_name', 'unknown')
            
            # 构建保存路径
            custom_appname = f"{app_name_real}_{package_name}_{app_version}.apk"
            download_dir = os.path.join("F:\\apk20250917\\vivoDownloads", custom_appname)
            
            # 下载APK文件
            return download_file(download_url, download_dir, headers)
        else:
            logging.warning(f"未找到应用 '{app_name}' 的数据")
            return False
    except Exception as e:
        logging.error(f"处理应用 '{app_name}' 时发生错误: {e}")
        return False

def main():
    # 目标URL
    base_url = "https://h5-api.appstore.vivo.com.cn/h5appstore/search/sug-list?imei=1234567890&av=18&app_version=2100&pictype=webp&h5_websource=h5appstore&word="
    
    # 设置请求头，模拟浏览器
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
        'Content-Type': 'application/json',
        'host': 'h5-api.appstore.vivo.com.cn',
        'origin': 'https://h5-api.appstore.vivo.com.cn',
        'referer': 'https://h5.appstore.vivo.com.cn/'
    }

    # 确保下载目录存在
    base_path = "F:\\apk20250917\\"
    download_path = os.path.join(base_path, "vivoDownloads")
    os.makedirs(base_path, exist_ok=True)
    os.makedirs(download_path, exist_ok=True)

    input_file = base_path + 'vivo.xlsx'
    # 读取 Excel 文件
    df = pd.read_excel(input_file)
    app_names = df['appname'].tolist()
    
    logging.info(f"开始下载 {len(app_names)} 个应用，最大并发数: 5")
    logging.info(f"文件将保存到 {download_path}")
    
    # 使用线程池并发下载，最多5个并发任务
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # 提交所有下载任务
        futures = []
        for app_name in app_names:
            future = executor.submit(process_app, app_name, base_url, headers)
            futures.append((future, app_name))
        
        # 处理完成的任务
        completed = 0
        failed = 0
        skipped = 0
        
        for future, app_name in zip(
            concurrent.futures.as_completed([f for f, _ in futures]), 
            [a for _, a in futures]
        ):
            try:
                success = future.result()
                if success:
                    logging.info(f"应用 '{app_name}' 处理完成")
                    completed += 1
                else:
                    logging.warning(f"应用 '{app_name}' 处理失败")
                    failed += 1
            except Exception as e:
                logging.error(f"处理应用 '{app_name}' 时发生异常: {e}")
                failed += 1
    
    logging.info(f"下载完成: 成功 {completed} 个, 失败 {failed} 个")

if __name__ == "__main__":
    start_time = time.time()
    main()
    elapsed_time = time.time() - start_time
    logging.info(f"总耗时: {elapsed_time:.2f} 秒")

